/*
* Author: Chris Seguin
*
* This software has been developed under the copyleft
* rules of the GNU General Public License. Please
* consult the GNU General Public License for more
* details about use and distribution of this software.
*/
package org.acm.seguin.refactor.type;
import java.io.File;
import java.util.Iterator;
import java.util.LinkedList;
import org.acm.seguin.parser.ast.ASTClassBody;
import org.acm.seguin.parser.ast.ASTClassDeclaration;
import org.acm.seguin.parser.ast.ASTCompilationUnit;
import org.acm.seguin.parser.ast.ASTImportDeclaration;
import org.acm.seguin.parser.ast.ASTName;
import org.acm.seguin.parser.ast.ASTPackageDeclaration;
import org.acm.seguin.parser.ast.ASTTypeDeclaration;
import org.acm.seguin.parser.ast.ASTUnmodifiedClassDeclaration;
import org.acm.seguin.parser.ast.SimpleNode;
import org.acm.seguin.pretty.PrettyPrintFile;
import org.acm.seguin.refactor.RefactoringException;
import org.acm.seguin.refactor.method.AddConcreteMethod;
import org.acm.seguin.refactor.method.AddConstructor;
import org.acm.seguin.refactor.method.AddMethodTypeVisitor;
import org.acm.seguin.summary.MethodSummary;
import org.acm.seguin.summary.PackageSummary;
import org.acm.seguin.summary.TypeDeclSummary;
import org.acm.seguin.summary.TypeSummary;
import org.acm.seguin.summary.query.GetPackageSummary;
import org.acm.seguin.summary.query.GetTypeSummary;
import org.acm.seguin.summary.query.SamePackage;
import org.acm.seguin.summary.query.TopLevelDirectory;
/**
* This object creates a class from nothing. It is responsible for building
* up the parse tree from scratch to create a new class.
*
*@author Chris Seguin
*/
public class CreateClass {
private TypeSummary typeSummary;
private String newClassName;
private boolean isParent;
private boolean isAbstract;
private boolean isFinal;
private String packageNameString;
private String scope;
/**
* Constructor for the CreateClass object
*
*@param existing The existing class we are building upon
*@param className The name of the new class
*@param parent Are we building a parent or child from the existing
* type
*@param packageName the name of the package that the class is in
*/
public CreateClass(TypeSummary existing, String className,
boolean parent, String packageName)
{
typeSummary = existing;
newClassName = className;
isParent = parent;
isAbstract = true;
isFinal = false;
packageNameString = packageName;
scope = "";
}
/**
* Constructor for the CreateClass object
*
*@param existing The existing class we are building upon
*@param className The name of the new class
*@param parent Are we building a parent or child from the existing type
*/
public CreateClass(TypeSummary existing, String className,
boolean parent)
{
typeSummary = existing;
newClassName = className;
isParent = parent;
isAbstract = true;
isFinal = false;
packageNameString = null;
scope = "";
}
/**
* Constructor for the CreateClass object
*/
CreateClass()
{
typeSummary = null;
newClassName = null;
}
/**
* Sets the PackageName attribute of the CreateClass object
*
*@param value The new PackageName value
*/
public void setPackageName(String value)
{
packageNameString = value;
}
/**
* Sets the Scope attribute of the CreateClass object
*
*@param value The new Scope value
*/
public void setScope(String value)
{
scope = value;
}
/**
* Sets the Abstract attribute of the CreateClass object
*
*@param way The new Abstract value
*/
public void setAbstract(boolean way)
{
isAbstract = way;
}
/**
* Sets the Final attribute of the CreateClass object
*
*@param way The new Final value
*/
public void setFinal(boolean way)
{
isFinal = way;
}
/**
* Creates the the designated class
*
*@return Description of the Returned Value
*@exception RefactoringException Description of Exception
*/
public File run() throws RefactoringException
{
if (newClassName == null) {
throw new RefactoringException("No class name specified");
}
if (typeSummary == null) {
throw new RefactoringException("No type to build upon");
}
if (packageNameString == null) {
packageNameString = GetPackageSummary.query(typeSummary).getName();
}
// Create the AST
ASTCompilationUnit root = new ASTCompilationUnit(0);
// Create the package statement
int nextIndex = 0;
if ((packageNameString != null) && (packageNameString.length() > 0)) {
ASTPackageDeclaration packDecl = createPackageDeclaration();
root.jjtAddChild(packDecl, 0);
nextIndex++;
}
TypeSummary parentSummary = null;
ASTName parentName;
if (isParent) {
TypeDeclSummary parentDecl = typeSummary.getParentClass();
parentSummary = GetTypeSummary.query(parentDecl);
if (parentSummary == null) {
parentSummary = GetTypeSummary.query(
PackageSummary.getPackageSummary("java.lang"),
"Object");
}
}
else {
parentSummary = typeSummary;
}
parentName = getNameFromSummary(parentSummary);
// If necessary, create the import statement
int typeIndex = nextIndex;
boolean added = addImportStatement(parentSummary, parentName, root, nextIndex);
if (added) {
typeIndex++;
parentName = new ASTName(0);
parentName.addNamePart(parentSummary.getName());
}
// Create the class
ASTTypeDeclaration td = createTypeDeclaration(parentName);
root.jjtAddChild(td, typeIndex);
addConstructors(parentSummary, root);
if (!isAbstract) {
addMethods(typeSummary, root);
}
// Print this new one
File dest = print(newClassName, root);
return dest;
}
/**
* Converts the type summary into a name
*
*@param summary the summary
*@return the name
*/
ASTName getNameFromSummary(TypeSummary summary)
{
ASTName name = new ASTName(0);
if ((summary == null) || summary.getName().equals("Object")) {
name.fromString("Object");
}
else {
PackageSummary packageSummary = getPackageSummary(summary);
if (packageSummary.isTopLevel()) {
name.fromString(summary.getName());
}
else if (!isSamePackage(packageNameString, summary)) {
name.fromString(packageSummary.getName() + "." + summary.getName());
}
else {
name.fromString(summary.getName());
}
}
return name;
}
/**
* Gets the SamePackage attribute of the AddAbstractParent object
*
*@param parentSummary Description of Parameter
*@param packageName Description of Parameter
*@return The SamePackage value
*/
boolean isSamePackage(String packageName, TypeSummary parentSummary)
{
return (parentSummary != null) &&
SamePackage.query(packageName, parentSummary);
}
/**
* Creates the package declaration
*
*@return the package declaration
*/
ASTPackageDeclaration createPackageDeclaration()
{
ASTPackageDeclaration packDecl = new ASTPackageDeclaration(0);
ASTName packName = new ASTName(0);
packName.fromString(packageNameString);
packDecl.jjtAddChild(packName, 0);
return packDecl;
}
/**
* Adds the import statement and returns true if the import statement was
* necessary
*
*@param parentSummary the parent summary
*@param parentName the parent name
*@param root the tree being built
*@return true if the import statement was added
*/
boolean addImportStatement(TypeSummary parentSummary,
ASTName parentName, ASTCompilationUnit root,
int index)
{
if (!isImportRequired(parentSummary)) {
return false;
}
// Create the import statement
ASTImportDeclaration importDecl = new ASTImportDeclaration(0);
importDecl.jjtAddChild(parentName, 0);
root.jjtAddChild(importDecl, index);
return true;
}
/**
* Creates the type declaration
*
*@param grandparentName Description of Parameter
*@return the modified class
*/
ASTTypeDeclaration createTypeDeclaration(ASTName grandparentName)
{
ASTTypeDeclaration td = new ASTTypeDeclaration(0);
ASTClassDeclaration cd = createModifiedClass(grandparentName);
td.jjtAddChild(cd, 0);
return td;
}
/**
* Creates the modified class
*
*@param grandparentName The name of the parent class
*@return the modified class
*/
ASTClassDeclaration createModifiedClass(ASTName grandparentName)
{
ASTClassDeclaration cd = new ASTClassDeclaration(0);
if (isAbstract) {
cd.addModifier("abstract");
}
if (isFinal) {
cd.addModifier("final");
}
if (scope.length() > 0) {
cd.addModifier(scope);
}
ASTUnmodifiedClassDeclaration ucd = createClassBody(newClassName, grandparentName);
cd.jjtAddChild(ucd, 0);
return cd;
}
/**
* Creates the body. The protection level is package so it can be easily
* tested.
*
*@param parentName Description of Parameter
*@param grandparentName Description of Parameter
*@return the class
*/
ASTUnmodifiedClassDeclaration createClassBody(String parentName, ASTName grandparentName)
{
ASTUnmodifiedClassDeclaration ucd = new ASTUnmodifiedClassDeclaration(0);
ucd.setName(parentName);
ucd.jjtAddChild(grandparentName, 0);
ucd.jjtAddChild(new ASTClassBody(0), 1);
return ucd;
}
/**
* Prints the file
*
*@param name The name of the object
*@param root The root of the tree
*@return The file that the parse tree was written to
*/
File print(String name, SimpleNode root)
{
File parent = getDirectory();
File destFile = new File(parent, name + ".java");
try {
(new PrettyPrintFile()).apply(destFile, root);
}
catch (Throwable thrown) {
thrown.printStackTrace(System.out);
}
return destFile;
}
/**
* Determines if we need to add an import
*
*@param parentSummary the parent summary
*@return true if the import is necessary
*/
private boolean isImportRequired(TypeSummary parentSummary)
{
return !isSamePackage(packageNameString, parentSummary) &&
!isSamePackage("java.lang", parentSummary);
}
/**
* Gets the package summary
*
*@param base The type whose package was are concerned about
*@return the package summary
*/
private PackageSummary getPackageSummary(TypeSummary base)
{
return GetPackageSummary.query(base);
}
/**
* Gets the SameParent attribute of the AddAbstractParent object
*
*@param one Description of Parameter
*@param two Description of Parameter
*@return The SameParent value
*/
private boolean isSameParent(TypeSummary one, TypeSummary two)
{
if (isObject(one)) {
return isObject(two);
}
if (isObject(two)) {
return false;
}
return one.equals(two);
}
/**
* Gets the Object attribute of the AddAbstractParent object
*
*@param item Description of Parameter
*@return The Object value
*/
private boolean isObject(TypeSummary item)
{
if (item == null) {
return true;
}
if (item.getName().equals("Object")) {
return true;
}
return false;
}
/**
* Creates a file object pointing to the directory that this class should be
* created into
*
*@return the directory
*/
private File getDirectory()
{
return TopLevelDirectory.getPackageDirectory(typeSummary, packageNameString);
}
/**
* Adds the constructors
*
*@param parentType The feature to be added to the Constructors attribute
*@param root The feature to be added to the Constructors attribute
*/
private void addConstructors(TypeSummary parentType, SimpleNode root)
{
Iterator iter = parentType.getMethods();
if (iter != null) {
while (iter.hasNext()) {
MethodSummary next = (MethodSummary) iter.next();
if (next.isConstructor()) {
AddConstructor ac = new AddConstructor(next, newClassName);
ac.update(root);
AddMethodTypeVisitor amtv = new AddMethodTypeVisitor(false);
amtv.visit(next, root);
}
}
}
}
/**
* Adds the methods
*
*@param type The feature to be added to the Methods attribute
*@param root The feature to be added to the Methods attribute
*/
private void addMethods(TypeSummary type, SimpleNode root)
{
AbstractMethodFinder finder = new AbstractMethodFinder(type);
LinkedList list = finder.getList();
Iterator iter = list.iterator();
while (iter.hasNext()) {
MethodSummary next = (MethodSummary) iter.next();
AddConcreteMethod ac = new AddConcreteMethod(next);
ac.update(root);
AddMethodTypeVisitor amtv = new AddMethodTypeVisitor(false);
amtv.visit(next, root);
}
}
}